home *** CD-ROM | disk | FTP | other *** search
/ AppleScript - The Beta Release / AppleScript - The Beta Release.iso / Documentation / User Documentation / AppleScript Lang Prog Notes / Error Handling in AS next >
Encoding:
Text File  |  1992-11-23  |  8.8 KB  |  308 lines  |  [ttro/ttxt]

  1. Error Handling in AppleScript
  2. =============================
  3. Warren Harris    11/22/92
  4.  
  5. AppleScript has built-in facilities for handling errors.  Errors (a.k.a.
  6. exceptions) raised during the processing of a script cause non-local transfer
  7. of control to an "error handler" occuring in the dynamic call chain of the 
  8. computation.  (Huh?)  This means that you never have to write code that checks
  9. for error numbers as return values.
  10.  
  11. Quick, and example, before they fall asleep...
  12.  
  13. Perhaps the simplest example of a script that deals with errors is the call to
  14. "error" itself:
  15.  
  16.     error "Hello there."
  17.     --! Error: Hello there. (-2700)
  18.  
  19. Here, the result is not a returned value, but a message displayed in Toy 
  20. Surprise's "execution error" dialog box.  The number in parentheses is the 
  21. actual runtime error code (-2700 is "user error", i.e. the script called the
  22. error statement without any particular error number -- we learn about particular
  23. error numbers later).  Errors are also raised when an illegal operation is
  24. performed:
  25.  
  26.     {} + 234
  27.     --! Can't make {} into a number.
  28.  
  29. That may not seem like a big deal, but it comes in really handy when you're in
  30. the middle of a large computation and suddenly decide it's impossible to
  31. proceed.  Calling error causes the script to be aborted, no matter where it is
  32. in the middle of computation.  Let's put one in a subroutine:
  33.  
  34.     on fact(n)
  35.         if n < 0
  36.             error "Can't take fact of " & n & "."
  37.         else if n = 0
  38.             return 1
  39.         else
  40.             return n * fact(n - 1)
  41.         end
  42.     end
  43.     
  44.     fact(3)
  45.     --> 6
  46.     
  47.     fact(-4)
  48.     --! Error: Can't take fact of -4. (-2700)
  49.  
  50. Now even if we embed this subroutine in another subroutine and an error is 
  51. raised, we can abort:
  52.     
  53.     on factList(aList)
  54.         set facts to {}    
  55.         repeat with x in aList
  56.             set facts to facts & fact(x)
  57.         end
  58.     end
  59.  
  60.     factList({3, 4, 5, 5})
  61.     --> {6, 24, 120, 120}
  62.     
  63.     factList({3, 4, 5, -5})
  64.     --! Error: Can't take fact of -5. (-2700)
  65.  
  66.     factList({3, 4, 5, {}, -5})
  67.     --! Can't make {} into a number.
  68.  
  69. Now suppose we don't want to just jump back to the top-level with an error
  70. dialog if factList gets an error, but we want to return the empty list if any
  71. error occurred.  This can be done by adding an error handler after the call to
  72. factList:
  73.  
  74.     factList({3, 4, 5, {}, -5})
  75.     on error
  76.         return {}
  77.     end
  78.     --> {}
  79.  
  80. Note that these have to be executed as a unit (which they will be if you're 
  81. using Toy Surprise).  The error handler looks like a handler that occurs inter-
  82. mingled with other script statements.  However, it's not a normal handler.  It
  83. may be placed in the middle of scripts whereas other handlers cannot.  For
  84. example, we could fold this empty-list-returning behavior into the factList
  85. function:
  86.  
  87.     on factList(aList)
  88.         set facts to {}    
  89.         repeat with x in aList
  90.             set facts to facts & fact(x)
  91.         end
  92.         on error
  93.             return {}
  94.         end
  95.     end
  96.  
  97. and achieve the same effect as the previous example (this is a little less 
  98. flexible though, because factList will now never raise an error, and the
  99. script writer won't get a chance to deal with its errors in different ways).
  100.  
  101. Error handler statements are also different from normal handlers in that they
  102. are only called when an error occurs.  This error can occur anywhere in the 
  103. statements that come before the error handler.  If any of the preceeding 
  104. statements are subroutine invocations (message sends) then the statements that
  105. they call will also be handled by the error handler, and the subroutines that
  106. they call, etc.  This is called the "dynamic scope" of the computation.
  107.  
  108. Let's change our factList example a little to have it insert zeros into the list
  109. for bogus input values:
  110.  
  111.     on factList(aList)
  112.         set facts to {}    
  113.         repeat with x in aList
  114.             set facts to facts & fact(x)
  115.             on error
  116.                 set facts to facts & 0
  117.             end
  118.         end
  119.     end
  120.     
  121.     factList({3, 4,-4, 5})
  122.     --> {6, 24, 0, 120}
  123.  
  124. Notice that the computation continues after the error handler just as if the 
  125. statement has ated normally -- in this case at the end of the repeat loop which
  126. causes it to immediately loop back to the beginning.
  127.  
  128. There are several parameters available to an error handler.  For one, we can
  129. catch the error message in the direct parameter.  Suppose we wanted to put
  130. the error message in the result list for each of the bogus input values.  We
  131. could write:
  132.  
  133.     on factList(aList)
  134.         set facts to {}    
  135.         repeat with x in aList
  136.             set facts to facts & fact(x)
  137.             on error msg
  138.                 set facts to facts & msg
  139.             end
  140.         end
  141.     end
  142.  
  143.     factList({3, 4,-4, {}, 5})
  144.     --> {6, 24, "Can't take fact of -4.", "Can't make {} into a number.", 120}
  145.  
  146. We could compare against the error string if we wished to determine exactly
  147. which error had been raised and do something different in each case.  However, 
  148. there's a better way to do this.  Errors can also be made to return error
  149. numbers.  In the case of illegal operations, these numbers are predefined.  In
  150. the case of user errors, the user may supply any number.  Let's rewrite fact
  151. to return an error number too:
  152.  
  153.     on fact(n)
  154.         if n < 0
  155.             error "Can't take fact of " & n & "." number -234
  156.         else if n = 0
  157.             return 1
  158.         else
  159.             return n * fact(n - 1)
  160.         end
  161.     end
  162.  
  163. We can catch the error in factList in a similar fashion.  Here we return zero
  164. for the negative values to fact error, and insert the error message for all
  165. other errors:
  166.  
  167.     on factList(aList)
  168.         set facts to {}    
  169.         repeat with x in aList
  170.             set facts to facts & fact(x)
  171.             on error msg number n
  172.                 if n = -234
  173.                     set facts to facts & 0
  174.                 else
  175.                     set facts to facts & msg
  176.                 end
  177.             end
  178.         end
  179.     end
  180.  
  181.     factList({3, 4,-4, {}, 5})
  182.     --> {6, 24, 0, "Can't make {} into a number.", 120}
  183.  
  184. (Ok, so I said you'd never have to check error numbers again... Well, you only
  185. have to check them if you really care about them.)
  186.  
  187. Besides the error message and error number, you can also get ahold of the
  188. error object and any partial result.  The error object is the "object" that
  189. caused the error: an actor, an application object, etc.  The error object can
  190. be captured in the "from" parameter:
  191.  
  192.     actor "fred"
  193.         on foo()
  194.             error number -23
  195.         end
  196.     end
  197.     
  198.     actor "bob"
  199.         on foo()
  200.             error number -23
  201.         end
  202.     end
  203.  
  204. Here we define two actors, fred and bob that both respond to the same message,
  205. foo.  Whichever one of them happens to receive the message at runtime will
  206. raise the same error, -23, but the error object will be bound to fred or bob,
  207. respectively:
  208.  
  209.     set x to actor "fred"
  210.     tell x to foo()
  211.     on error number n from errObj
  212.         return {n, errObj}
  213.     end
  214.     --> {-23, actor "fred"}
  215.     
  216.     set x to actor "bob"
  217.     tell x to foo()
  218.     on error number n from errObj
  219.         return {n, errObj}
  220.     end
  221.     --> {-23, actor "bob"}
  222.  
  223. Similarly, when sending events to remote applications, we can catch the error
  224. object (the object specifier) that caused the error:
  225.  
  226.     tell application "Quill"
  227.         close window 99
  228.     end
  229.     on error from f number n
  230.         {n, f}
  231.     end
  232.     --> {-2705, window 99 of application "Quill"}
  233.     
  234. Occasonally, when using whose-clauses the OSL will return partial results.
  235. These can be captured in the "partial results" parameter to an error handler:
  236.  
  237.     tell window 1 of application "Quill"
  238.         get every word whose character 3 = "e"
  239.     end
  240.     on error number n from f partial result p
  241.         {n, f, p}
  242.     end
  243.  
  244. If window 1 of application "Quill" contained the text "these are my words,"
  245. then the partial result, p, above would be the list {"these", "are"} -- the
  246. two words that succeeded before the third word didn't have a third character.
  247. (I'm not sure that this is working right for this release.)  Script writers
  248. can of course use the partial result explicitly when an error is raised:
  249.  
  250.     on factList(aList)
  251.         set facts to {}    
  252.         repeat with x in aList
  253.             set facts to facts & fact(x)
  254.             on error msg number n
  255.                 error msg number n partial result facts
  256.             end
  257.         end
  258.     end
  259.  
  260.     factList({3, 4,-4, {}, 5})
  261.     on error partial result p
  262.         p
  263.     end
  264.     --> {6, 24}
  265.  
  266. One final note.  Whenever a remote application, applet, droplet, or system
  267. command returns an error number, an error is raised in AppleScript that may be
  268. caught with an error handler.  For example, the "choose application" dialog
  269. returns a user canceled error if the user clicks "Cancel".  This causes an error
  270. to be raised in AppleScript:
  271.  
  272.     choose application with prompt "What should I quit?"
  273.     tell result
  274.         quit
  275.     end
  276.     on error
  277.         display dialog "Ok, I won't."
  278.     end
  279.  
  280. There's one exception:  Display dialog itself doesn't return an error if the
  281. user clicks "cancel".  Instead, it just returns a record with the label
  282. "button returned" which must be tested for the string "Cancel".  In other words,
  283. you can't say this:
  284.  
  285.     display dialog "Are you there?"
  286.     on error
  287.         ... do error stuff...
  288.     end
  289.  
  290. You have to say this instead:
  291.  
  292.     display dialog "Are you there?"
  293.     if button returned of result = "Cancel"
  294.         ... do error stuff...
  295.     end
  296.  
  297. A future version of display dialog may behave differently.  But... you can
  298. always write your own:
  299.  
  300.     on confirm(msg)
  301.         display dialog msg
  302.         if button returned of result = "Cancel"
  303.             error number -128    (* userCanceledErr *)
  304.         end
  305.     end
  306.  
  307. Happy error handling.
  308.